package com.uxsino.commons.lic;

import com.alibaba.fastjson.JSON;
import com.google.common.base.Strings;
import com.google.common.io.Files;
import com.google.common.primitives.Bytes;
import com.uxsino.commons.utils.Dates;
import com.uxsino.commons.utils.RSA;
import com.uxsino.commons.utils.TimeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;

/**
 * license 验证
 */
public final class Lic {
    private static final Logger LOG = LoggerFactory.getLogger(Lic.class);
    private byte[] data;// license 文件
    private static final String key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFAVPSJL3cYC5PDHE/c+TPTueKsXdOIJ/okomcrgHbWOiRMf3EQ2X2kpUYH8S4Qlaph7AqphRPxUHSgRiK8DWoJaZQ9nLFcVHb7zwhCquEIGGGtcIYGJ/55+E4+fwHLEMtAbVPIjs1Y8Zp+cw7aL5KEB3w5WVSUUofi21Xn+lusQIDAQAB";

    private int keySize = 1024;

    private String decodedData;

    private byte[]  signPart;
    private byte[] dataPart;

    private String msg;

    public String msg(){
        return this.msg;
    }

    public static Lic of(String filePath)throws Exception{
        if(Strings.isNullOrEmpty(filePath)){
        throw new RuntimeException("simo license error: license file is not exist.");
    }
    File file = new File(filePath);

        return of(file);
    }


    public static Lic of(File file) throws Exception{
        if(file == null || !file.exists() || !file.isFile()){
            throw new RuntimeException("simo license error: file is not exists.");
        }
        // 获取当前时间
        Date now = new Date();
        // 容错，可以调节时间1天
        Calendar nowCalendar = Calendar.getInstance();
        nowCalendar.setTime(now);
        nowCalendar.add(Calendar.DAY_OF_MONTH, +1);
        Date faultTolerantDate = nowCalendar.getTime();
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        // 获取simo.lic最后修改时间
        Date fileModified = new Date(file.lastModified());
        // 判断当前容错时间是否大于simo.lic最后修改时间
        if (faultTolerantDate.after(fileModified)) {
            // 设置simo.lic文件最后修改时间为当前时间
            file.setLastModified(Dates.from(now).toTimeInMillis());
        }else {
            throw new RuntimeException("simo license error: system time error.");
        }

        byte[] cache = new byte[1024];
        InputStream io = null;
        try {
            io = new FileInputStream(file);
            int count = 0;
            while ((count = io.read(cache)) != -1){
                out.write(cache, 0, count);
            }
        }catch (Exception e){
            LOG.error("simo license error: {}", e);
        }finally {
            if(io != null){
                try {
                    io.close();
                }catch (Exception e){}
            }
        }

        Lic lic =  new Lic(out.toByteArray());

        try {
            out.close();
        }catch (Exception e){}

        return lic;
    }

    public static Lic of(byte[] data) throws Exception{
        return new Lic(data);
    }

    private Lic(byte[] data) throws Exception{
        this.data = data;
        if(this.data.length <= 0){
            throw new RuntimeException("simo license error: file is empty.");
        }
        RSA rsa = RSA.of().keySize(this.keySize);
        byte[] originBytes = rsa.decode(this.data, key);

        // 解密拆分data 和 sign
        int sp = -1;

        for (int index = 0; index < originBytes.length; index++) {
            if (originBytes[index] == '\n') {
                if (RSA.isBase64(Arrays.copyOfRange(originBytes, index+1, originBytes.length))) {
                    sp = index;
                    break;
                }
            }
        }


        LOG.info("[license]length -------------------------------------》："+ data.length);
        LOG.info("[license]分割符位置-------------------------------------》："+ sp);
        if(sp == -1){
            LOG.error("[license] 数据证书错误，不存在签名或者数据");
            throw new LicException("数据证书错误，不存在签名或者数据");
        }
        this.dataPart = Arrays.copyOfRange(originBytes, 0, sp);//不包含分隔符
        this.signPart = Arrays.copyOfRange(originBytes, sp + 1, originBytes.length);//不包含分隔符
    }

    /**
     * 校验数据是否和当前license 文件相同
     * @param data
     * @return
     */
    public boolean eq(byte[] data){
        if(data == null || data.length == 0){
            return false;
        }
        if(this.data == null || this.data.length == 0 || this.data.length != data.length){
            return false;
        }
        if(!Arrays.equals(this.data, data)){
            return false;
        }
        return true;
    }

    public boolean valid(String code){
        if(Strings.isNullOrEmpty(code)){
            LOG.error("[simo license] code is empty");
            msg = "error license code";
            return false;
        }

        try {
            boolean legal = sign();
            if(!legal){
                msg = "illegal signature";
                return false;
            }
            AppProperty property = get(AppProperty.class);
            if(property == null){
                msg = "Non-standard certificate format";
                return false;
            }

            // 先校验日期是否过期，给出用户友善的提醒
            if (!property.validation()) {
                msg = "证书已过期";
                return false;
            }
            return property.validation(code);
        } catch (Exception e) {
            LOG.error("simo license parse content error： {}", e);
        }
        return false;
    }

    public boolean valid(){
        try {
            boolean legal = sign();
            if(!legal){
                return false;
            }
            AppProperty property = get(AppProperty.class);
            if(property == null){
                return false;
            }
            return property.validation();
        } catch (Exception e) {
            LOG.error("simo license parse content error： {}", e);
        }
        return false;
    }

    private boolean sign() throws LicException{
        RSA rsa = RSA.of().keySize(this.keySize);
        try {
            if (rsa.validate(this.dataPart, key, new String(this.signPart, "UTF-8"))) {
                return true;
            } else {
                LOG.error("[license] 数字签名认证失败.{}");
            }
        }catch (Exception e){
            throw new LicException(e);
        }
        return false;
    }

    public <T> T get(Class<T> cls)throws LicException{
        if(!sign()){
            return null;
        }
        try {
            if(Strings.isNullOrEmpty(this.decodedData)){
                byte[] result = RSA.of().decode(dataPart, key);
                this.decodedData = new String(result, "UTF-8");
            }
        }catch (Exception e){
            LOG.error("[license] 数据解码失败： {}", e);
        }

        if(!Strings.isNullOrEmpty(this.decodedData)){
            return JSON.parseObject(this.decodedData, cls);
        }
        return null;
    }

    public byte[] getContent(){
        return this.data;
    }

    public void writeToFile(File file) throws Exception{
        Files.write(this.data, file);
    }

    public class LicException extends Exception{
        public LicException() {
            super();
        }

        public LicException(String message) {
            super(message);
        }

        public LicException(Throwable cause) {
            super(cause);
        }
    }



















}
